﻿using System;
using System.Security.Cryptography;

namespace SkeinLibManaged
{   
    //THIS MODULE VERY UNTESTED, JUST THROWN TOGETHER
    //PLEASE COMMENT AND UPDATE ON CODEPLEX
    //http://skeinlibmanaged.codeplex.com/


    class Skein256 : System.Security.Cryptography.HashAlgorithm
    {   //CONSTANTS
        // these first ones are not needed, the CONFIG step is precomputed
        //private const UInt32 SKEIN_VERSION	  = 0x00000001;
        //private const UInt32 SKEIN_SCHEMA_ID  = 0x33414853;   // (0x33414853) = "SHA3" in ASCII
        //private const UInt64 SKEIN_VS         = (UInt64)(((UInt64)SKEIN_VERSION << 32) + SKEIN_SCHEMA_ID);

        private const UInt64 SKEIN_FLAG_FIRST	= 0x4000000000000000;
        private const UInt64 SKEIN_FLAG_FINAL	= 0x8000000000000000;

        private const UInt64 SKEIN_TYPE_MSG		= 0x3000000000000000;
        private const UInt64 SKEIN_TYPE_OUTPUT  = 0x3F00000000000000;
        private const uint BYTEWIDTH = 32;  //constant for 256-bit state-width
        private const uint WORDWIDTH = BYTEWIDTH / sizeof(UInt64);

        //METHODS
        override protected void HashCore(byte[] input, int startIndex, int length)
        {   //BEGIN MSG
            //the logic in the FOR statement allows the main loop to run AT LEAST ONCE even if
            // the input is zero length, and not lock into an infinite loop when it's done!!!!
            for (ulong i = 0; (i * BYTEWIDTH) < (uint)length || (length == 0 && i == 0); i++)
            {   //process a block
                _tweak[0] = i * BYTEWIDTH;
                byte[] buf = new byte[BYTEWIDTH];
                for (uint j = 0; j < BYTEWIDTH; j++)
                {   //process a byte
                    if ((ulong)(j  + (i * BYTEWIDTH)) < (uint)length)
                    {
                        buf[j] = input[(i * BYTEWIDTH) + j + (uint)startIndex];
                        _tweak[0] += 1;  //count each byte
                    }
                    else	// or zero it out
                        buf[j] = 0;
                }
                if ((i + 1) * BYTEWIDTH >= (uint)length)
                    _tweak[1] += SKEIN_FLAG_FINAL;
                System.Buffer.BlockCopy(buf, 0, _transport, 0, buf.Length);
                _state = ThreeFish.ThreeFish256Words(_transport, _state, _tweak, true);
                _tweak[1] &= ~SKEIN_FLAG_FIRST;
            }
            //END MSG

            //BEGIN OUTPUT
            _tweak[0] = 8; // <<<<<<<<<<<<<<WHY???????? not explained well in the white paper or ref code
            _tweak[1] = SKEIN_FLAG_FIRST + SKEIN_TYPE_OUTPUT + SKEIN_FLAG_FINAL;
            _ClearWords(_transport);
            _transport = ThreeFish.ThreeFish256Words(_transport, _state, _tweak, true); 
            System.Buffer.BlockCopy(_transport, 0, base.HashValue, 0, (int)BYTEWIDTH);
            //END OUTPUT

            _ClearWords(_transport);
            _ClearWords(_state);
            _ClearWords(_tweak);
            _transport = null;
            _state = null;
            _tweak = null;

            //TO DO:  Test this in real-world applications that use HashCore and HashFinal to see if this could cause an issue.
            //      the problem with Skein versus other hash algorithms, the last block is operated on with a flag.  This flag
            //      CANNOT be applied during the HashFinal process, becuase all of the available data has been processed.
            //      Perhaps one block length of data should be cached at all times to account for this????

            return;     
        }

        override protected byte[] HashFinal()
        {
            return base.HashValue;
        }

        override public void Initialize()
        {
            _state = new UInt64[] { 0x164290A9D4EEEF1D, 0x8E7EAF44B1B0CD15, 0xA8BA0822F69D09AE, 0x0AF25C5E364A6468 };
            _tweak = new UInt64[] { 0, SKEIN_FLAG_FIRST + SKEIN_TYPE_MSG };
            _transport = new UInt64[] { 0, 0, 0, 0 };

        }
       
        //PROPERTIES
        public readonly new bool CanTransformMultipleBlocks = true;
        public readonly new int HashSize = 256;
        public readonly new int InputBlockSize = 32;
        public readonly new int OutputBlockSize = 32;

        //BEHIND THE SCENES
        private UInt64[] _transport;
        private UInt64[] _state;
        private UInt64[] _tweak;
        
        private static void _ClearWords(UInt64[] inWords)
        {
            for (int i = inWords.GetLowerBound(0); i <= inWords.GetUpperBound(0); i++)
                inWords[i] = 0;
        }
    }
}
